﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using SharePoint.Orm.Helpers;

namespace SharePoint.Orm.SiteBuilders {
	/// <summary>
	/// For finding/adding a list.
	/// </summary>
	public class ListBuilder : BuilderBase<SPList> {
		/// <summary>
		/// Required.  The display name of the list.
		/// </summary>
		public string Name { get; set; }
		
		/// <summary>
		/// Optional.  If you leave this null it will copy the Description from the parent content type.  You can set this to empty string
		/// if you want the list description blank when the parent content type contains one.
		/// </summary>
		public string Description { get; set; }
		
		/// <summary>
		/// Optional, default = SPListTemplateType.GenericList.
		/// </summary>
		public SPListTemplateType ListTemplateType { get; set; }
		
		/// <summary>
		/// Required, although mutually exclusive with ContentTypes so you could use that instead.
		/// </summary>
		public SPContentType ContentType { get; set; }

		/// <summary>
		/// Optional.
		/// </summary>
		public IEnumerable<SPContentType> ContentTypes { get; set; }
		
		/// <summary>
		/// Whether the list supports content types.
		/// </summary>
		public bool ContentTypesEnabled { get; set; }
		
		/// <summary>
		/// Whether to display the list on the quick launch bar.
		/// </summary>
		public bool OnQuickLaunch { get; set; }
		
		/// <summary>
		/// Whether to enable attachments.
		/// </summary>
		public bool EnableAttachments { get; set; }
		
		/// <summary>
		/// Optional, default = true.  Whether to enable versioning.
		/// </summary>
		public bool EnableVersioning { get; set; }


		/// <summary>
		/// Optional, default = false.  Whether to enable minor versions.
		/// </summary>
		public bool EnableMinorVersions { get; set; }

		/// <summary>
		/// An value to identify a list.  This will be stored in the site properties for quick list retrieval.
		/// </summary>
		public string ListIdentifier { get; set; }

		/// <summary>
		/// Optional, default = false.  Set to true to hide the list from end users.
		/// </summary>
		public bool Hidden { get; set; }

		/// <summary>
		/// Retrieves the list description if there is one.  Otherwise retrieves the content type's description.
		/// </summary>
		/// <returns></returns>
		public string GetDescription() {
			return Description ?? (ContentType == null ? null : ContentType.Description);
		}

		/// <summary>
		/// Optional.  Allows adding custom actions prior to calling List.Update().
		/// </summary>
		public Action<SPList> CustomAction { get; set; }

		/// <summary>
		/// Initializes default values of properties (see descriptions in individual properties).
		/// </summary>
		public ListBuilder() {
			ContentTypesEnabled = true;
			OnQuickLaunch = false;
			EnableAttachments = true;
			EnableVersioning = true;
			EnableMinorVersions = false;
			ListTemplateType = SPListTemplateType.GenericList;
			ContentTypes = new List<SPContentType>();
		}

		public override SPList FindAdd() {
			if (string.IsNullOrEmpty(Name)) throw new SPOrmException("Name is required.");

			// refresh the site to avoid the dreaded SPException when you try to create more than about 10 lists: Save Conflict.  Your changes conflict with those made concurrently by another user. If you want your changes to be applied, click Back in your Web browser, refresh the page, and resubmit your changes.
			//	It seems heavy haned, but it seems to work, see: http://www.theartofsharepoint.com/2007/05/microsoftsharepointspexception-save.html
			RefreshSite();

			bool isDocumentLibrary = ListTemplateType == SPListTemplateType.DocumentLibrary;
			bool isSurvey = ListTemplateType == SPListTemplateType.Survey;
			if (isDocumentLibrary || isSurvey) {
				// attachments aren't allowed for document libraries or surveys, so assume 
				//	the user of this class forgot to update EnableAttachments's default value
				EnableAttachments = false;
			}

			SPList list = SPWebHelper.FindOrAddList(Site, Name, GetDescription(), ListTemplateType);
			Guid listId = list.ID;

            if (ContentTypes.Any()) {
                SetContentType(list, ContentTypes);
            } else if (ContentType != null) {
				SPListHelper.SetContentType(list, ContentType);
			} 

			list.ContentTypesEnabled = ContentTypesEnabled;
			list.OnQuickLaunch = OnQuickLaunch;
			list.EnableAttachments = EnableAttachments;
			list.EnableVersioning = EnableVersioning;
			list.EnableMinorVersions = EnableMinorVersions;
			list.Hidden = Hidden;

			if (CustomAction != null) {
				CustomAction(list);
			}

			list.Update();

			if (!string.IsNullOrEmpty(ListIdentifier)) {
				Site.Properties[ListIdentifier] = listId.ToString();
				Site.Properties.Update();
			}
			
			return list;
		}

		/// <summary>
		/// Sets the content type of a list.
		/// </summary>
		/// <param name="list"></param>
		/// <param name="contentTypesToAdd"></param>
		/// <returns></returns>
		public static bool SetContentType(SPList list, IEnumerable<SPContentType> contentTypesToAdd) {
			if (contentTypesToAdd == null) {
				throw new ArgumentNullException("contentTypesToAdd");
			}

			bool anyContentTypeAdded = false;

			foreach (SPContentType contentType in contentTypesToAdd) {
				bool contentTypeAdded = AddContentType(list, contentType);
				anyContentTypeAdded = anyContentTypeAdded || contentTypeAdded;
			}
			
			IList<SPContentType> contentTypesToDelete = new List<SPContentType>();
			foreach (SPContentType listsContentType in list.ContentTypes) {
				SPContentTypeId existingContentType = listsContentType.Id.Parent;
				if (!contentTypesToAdd.Any(ct => ct.Id == existingContentType)) {
					contentTypesToDelete.Add(listsContentType);
				}
			}

			bool anyContentTypesDeleted = RemoveContentTypes(list, contentTypesToDelete);

			bool anyChanged = anyContentTypeAdded || anyContentTypesDeleted;
			if (anyChanged) {
				list.Update();
			}
			return anyChanged;
		}

		private static bool AddContentType(SPList list, SPContentType contentType) {
			SPContentTypeId bestMatch = list.ContentTypes.BestMatch(contentType.Id);
			if (bestMatch.Parent != contentType.Id) {
				list.ContentTypes.Add(contentType);
				return true;
			}
			return false;
		}

		private static bool RemoveContentTypes(SPList list, IEnumerable<SPContentType> contentTypesToDelete) {
			bool changed = false;
			foreach (SPContentType contentTypeToDelete in contentTypesToDelete) {
				list.ContentTypes.Delete(contentTypeToDelete.Id);
				changed = true;
			}
			return changed;
		}
	}

	/// <summary>
	/// Compares two content types by Id.
	/// </summary>
	public class ContentTypeEqualityComparer : IEqualityComparer<SPContentType> {
		public bool Equals(SPContentType x, SPContentType y) {
			if (x == y) return true;
			if (x == null || y == null) return false;
			return x.Id == y.Id;
		}

		public int GetHashCode(SPContentType obj) {
			if (obj == null) return -1;
			return obj.Id.GetHashCode();
		}
	}
}
